package com.edu.learning.gc;

/**
 * -verbose:gc -Xmx20M -Xmn10M -XX:TargetSurvivorRatio=10 -XX:+PrintTenuringDistribution
 * -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxTenuringThreshold=4
 *
 * 对象分配先从Eden空间获取内存，当Eden区空间不足时，进行Minor，
 * 将Eden与From Survivor空间存活的对象，Copy到To Survivor空间，
 * 然后清空Eden与From Survivor空间，之后将To Survivor空间对象年龄加 1，
 * 并将To Survivor空间设置为From Survivor空间，保证Minor GC时To Survivor空间始终为空；
 * 而将年龄大于为15的存活对象放入老年代（默认是15，可通过-XX:MaxTenuringThreshold设定）
 * （为何一个对象最多经历15次ygc就会晋升到老年代，因为对象头里就分配了四个bit存age属性，最大为1111，即15）
 *
 * StringTable的bucket数量默认为60013 ，且每个bucket占用8个字节；Number of entries即 Hashtable的entry数量为2568786，
 * 因为我们String.intern( )了250w个不同的String对象；Average bucket size表示表示bucket中LinkedList的平均size，
 * Maximum bucket size 表示bucket中LinkedList最大的size，Average bucket size越大，说明Hashtable碰撞越严重，
 * 由于bucket数量固定为60013，随着StringTable添加的引用越来越多，碰撞越来越严重，YGC时间越来越长。
 * 设置StringTableSize合适的值，即bucket数量为期望的数量后，碰撞的概率明显降低，
 * 由Average bucket size和Maximum bucket size的值明显小于未配置StringTableSize参数时的值可知，且YGC时间也明显降低。
 * 另外, 最好通过BTrace分析是哪里频繁调用String.intern(), 确实String.intern()没有滥用的前提下, 再增大StringTableSize的值。
 * JVM中StringTable的rehash不会扩大bucket数量，而是在bucket不变的前提下，通过新的seed尝试摊平每个bucket中LinkedList的长度。
 * Java 层面触发不断调用到JNIHandles::make_weak_global, YGC的时间慢慢增涨，Java 层面有各种引用，
 * 包括 SoftReference，WeakReference 等，其中 WeakReference 可以保证在 GC 的时候不会阻碍其引用对象的回收，
 * 同样的在native代码里，我们也可以做类似的事情，有个叫做 JNIHandles::make_weak_global的方法来达到这样的效果。
 *
 * 使用 jstack 命令，最后一条输出里会看到类似 JNI global references: 328 的日志，这里其实就是打印了 JNI 里的两种全局引用总数，
 * 分别是 _global_handles 和 _weak_global_handles。在 JDK 层面的栈如下：
 *
 * at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
 * at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:975)
 * at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000)
 * at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1384)
 * at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:778)
 *
 * 最上面的 resolve 方法是一个 native 方法，这个方法发现可以直接调用到上面提到的 JNIHandles::make_weak_global 方法。
 *
 * #0  JNIHandles::make_weak_global (obj=...)
 * #1  0x00007ffff5e7e34a in InstanceKlass::add_member_name (this=0x10015ec28, mem_name=...)
 * #2  0x00007ffff60e3556 in Method
 * Handles::init_method_MemberName (mname=..., info=...)
 * #3  0x00007ffff60e8290 in MethodHandles::resolve_MemberName (mname=..., caller=..., __the_thread__=0x7ffff5651000)
 * #4  0x00007ffff60e84d2 in MHN_resolve_Mem (env=<optimized out>, igcls=<optimized out>, mname_jh=<optimized out>, caller_jh=0x0)
 *
 * JNIHandles::make_weak_global 方法其实就是创建了一个 JNI Weak Reference
 *
 * 查看JVM参数： jinfo -flag <name> pid
 * 查看所有支持动态修改的JVM参数： java -XX:PrintFlagsInitial | grep manageable 或者 HotSpotDiagnosticMXBean.getDiagnosticOptions()方法
 * 查看JVM参数列表: java -client -XX:+PrintFlagFinalBenchmark
 * 打印StringTable统计信息： -XX:+PrintStringTableStatics，在JVM进程退出时，打印出StringTable的统计信息到标准日志输出目录中
 *
 * @author ll
 * @Date 2018/1/9 18:00
 */
public class GcSurvivor {
    public static void main(String[] args) throws InterruptedException {
        // 这两个对象不会被回收, 用于在s0和s1不断来回拷贝增加age直到达到PretenureSizeThreshold晋升到old
        byte[] byte1m_1 = new byte[8 * 512 * 1024];
        byte[] byte1m_2 = new byte[8 * 512 * 1024];

        // YoungGC后, byte1m_1 和 byte1m_2的age为1
        youngGc(1);
        Thread.sleep(3000);

        // 再次YoungGC后, byte1m_1 和 byte1m_2的age为2
        youngGc(1);
        Thread.sleep(3000);

        // 第三次YoungGC后, byte1m_1 和 byte1m_2的age为3
        youngGc(1);
        Thread.sleep(3000);

        // 这次再ygc时, 由于byte1m_1和byte1m_2的年龄已经是3，且MaxTenuringThreshold=3, 所以这两个对象会晋升到Old区域,且ygc后, s0(from)和s1(to)空间中的对象被清空
        youngGc(1);
        Thread.sleep(3000);

        // main方法中分配的对象不会被回收, 不断分配是为了达到TargetSurvivorRatio这个比例指定的值, 即5M*60%=3M(Desired survivor size)，说明: 5M为S区的大小，60%为TargetSurvivorRatio参数指定，如下三个对象分配后就能够达到Desired survivor size
        byte[] byte1m_4 = new byte[1 * 1024 * 1024];
        byte[] byte1m_5 = new byte[1 * 1024 * 1024];
        byte[] byte1m_6 = new byte[1 * 1024 * 1024];

        // 这次ygc时, 由于s区已经占用达到了60%(-XX:TargetSurvivorRatio=60), 所以会重新计算对象晋升的age，计算公式为：min(age, MaxTenuringThreshold) = 1
        youngGc(1);
        Thread.sleep(3000);

        // 由于前一次ygc时算出age=1, 所以这一次再ygc时, byte1m_4, byte1m_5, byte1m_6就会晋升到Old区, 而不需要等MaxTenuringThreshold这么多次, 此次ygc后, s0(from)和s1(to)空间中对象再次被清空, 对象全部晋升到old
        youngGc(1);
        Thread.sleep(3000);

        System.out.println("-----------------");
    }

    private static void youngGc(int ygcTimes){
        for(int i=0; i<ygcTimes*40; i++) {
            byte[] byte1m = new byte[1 * 1024 * 1024];
        }
    }

}
